function [  ] = shuffle( this, obj )
% DataPartition::shuffle( this:DataPartition
%                         obj :{double, DataMatrix, DataSet}
%
% Description
% Generate new seen, unseen, training, validataion and test data partition
% Note that this function MUST rely on $obj$
%
% Input
% 1. this : @DataPartition object
% 2. obj : an object containing the label matrix $Y$
%
% Output
% 0.
%
% Syntax
% ~. this.shuffle(obj);
%
% Example
% ~. dataPartition.shuffle(Y);
% ~. dataPartition.shuffle(dmY);
% ~. dataPartition.shuffle(ds);

%% Check input

if nargin == 1
    error('BatErr: Invalid input arguments.');
end

switch class(obj)
    case 'double'
        Y = obj;
    case 'DataMatrix'
        Y = obj.mat;
        this.shuffle(Y);
        return
    case 'DataSet'
        Y = obj.dmY.mat;
        this.shuffle(Y)
        return
    otherwise
        error('BatErr: Invalid input type for $obj$.');
end

%% Preparation

% Pre-allocate free space
zzSeenId = cell(1, this.nRep);
zzUnsnId = cell(1, this.nRep);
zzTrnId = cell(1, this.nRep);
zzValId = cell(1, this.nRep);
zzTstId = cell(1, this.nRep);

%% K_FOLD

Y_code = transMat2Code(Y); % Convert into code form

for iRep = 1:this.nRep
    % Shuffle $[seen,unsn]IdMat$ in each repetition for $this.nFld$ folds
    switch this.type
        case 'k_fold'
            [zzSeenId_i, zzUnsnId_i] = shuffleKFold(Y_code, this.nFld);
        case 'hold_out'
            [zzSeenId_i, zzUnsnId_i] = shuffleHoldOut(Y_code, this.nFld);
        otherwise
            error('BatErr: Invalid shuffle type.');
    end
    zzSeenId{iRep} = zzSeenId_i;
    zzUnsnId{iRep} = zzUnsnId_i;
    
    % Pre-allocate free space
    zzTrnId{iRep} = cell(1, this.nFld);
    zzValId{iRep} = cell(1, this.nFld);
    zzTstId{iRep} = cell(1, this.nFld);
    for jFld = 1:this.nFld
        % Shuffle $[trn,val,tst]IdMat in each folds
        % for $this.nSubFld$ folds
        
        Y_code_i = Y_code;
        Y_code_i(zzUnsnId_i(jFld, :) == 1) = inf;
        switch this.type
            case 'k_fold'
                [zzSeenId_ij, zzUnsnId_ij] ...
                    = shuffleKFold(Y_code_i, this.nSubFld);
            case 'hold_out'
                [zzSeenId_ij, zzUnsnId_ij] ...
                    = shuffleHoldOut(Y_code_i, this.nSubFld);
            otherwise
                error('BatErr: Invalid shuffle type.');
        end
        
        % Training data is seen in folds and seen in subfolds
        zzTrnId_ij = repmat(zzSeenId_i(jFld, :), this.nSubFld, 1) ...
            & zzSeenId_ij;
        % Validation data is seen in folds and unseen in subfolds
        zzValId_ij = repmat(zzSeenId_i(jFld, :), this.nSubFld, 1) ...
            & zzUnsnId_ij;
        % Test data is unseen in folds
        zzTstId_ij = repmat(zzUnsnId_i(jFld, :), this.nSubFld, 1);
        
        zzTrnId{iRep}{jFld} = zzTrnId_ij;
        zzValId{iRep}{jFld} = zzValId_ij;
        zzTstId{iRep}{jFld} = zzTstId_ij;
    end
end

%%

this.zzSeenId = zzSeenId;
this.zzUnsnId = zzUnsnId;
this.zzTrnId = zzTrnId;
this.zzValId = zzValId;
this.zzTstId = zzTstId;

end

%%

function [ zzSeenId, zzUnsnId ] = shuffleHoldOut( Y_code, nFld )
%SHUFFLEHOLDOUT Shuffle data partition index with hold out strategy
%
% DataPartition;;shuffleHoldOut( Y_code:double,
%                              nFld  :int)
%              >>            [ zzSeenId:logical,
%                              zzUnsnId:logical ]
%
% Description
% Shuffle by `HOLD_OUT` strategy
%
% Input
% 1. Y_code : Code form for data partition genereted by
%             DataPartition::shuffle::transMat2Code( mat )
% 2. nFld   : Number of folds to shuffle
%             `1/nFld` will be held out for unseen data
%
% Output
% 1. zzSeenId : Seen data index for $nFld$ folds
% 2. zzUnsnId : Unseen data index for $nFld$ folds
%
% Syntax
% ~. [zzSeenId, zzUnsnId] = shuffleHoldOut(Y_code, nFld);
%
% Example

nObs = size(Y_code, 1);

zzSeenId = false(nFld, nObs);
zzUnsnId = false(nFld, nObs);

for iFld = 1:nFld
    [zSeenId_i, zUnsnId_i] = crossvalind('HoldOut', Y_code, 1/nFld);
    
    zzSeenId(iFld, :) = zSeenId_i;
    zzUnsnId(iFld, :) = zUnsnId_i;
end

end

%%

function [ seenIdMat, unsnIdMat ] = shuffleKFold( Y_code, nFld )
% DataPartition;;shuffleKFold( Y_code:double,
%                              nFld  :int)
%              >>            [ seenIdMat:logical,
%                              unsnIdMat:logical ]
%
%
% Description
% Shuffle by K_FOLD strategy
%
% Input
% 1. Y_code : Code form for data partition genereted by
%             DataPartition::shuffle::transMat2Code( mat )
% 2. nFld   : Number of folds to shuffle
%
% Output
% 1. seenIdMat : Seen data index for $nFld$ folds
% 2. unsnIdMat : Unseen data index for $nFld$ folds
%
% Syntax
% ~. [seenIdMat, unsnIdMat] = shuffle(Y_code, nFld);
%
% Example

nObs = size(Y_code, 1);

seenIdMat = false(nFld, nObs);
unsnIdMat = false(nFld, nObs);

cvId = crossvalind('Kfold', Y_code, nFld);

for iFld = 1:nFld
    seenId_i = cvId ~= iFld;
    unsnId_i = cvId == iFld;
    
    seenIdMat(iFld, :) = seenId_i;
    unsnIdMat(iFld, :) = unsnId_i;
end

end

%%

function [ code ] = transMat2Code( mat )
% DataPartition;;transMat2Code( mat:double )
%              >>             [ code:double ]
%
% Description
% Transform label matrix from matrix form to code form for partition
%
% Input
% 1. mat : Label matrix of double form
%
% Output
% 1. code : transformed label code for partition
%
% Syntax
% ~. code = transMat2Code(mat);
%
% Example
%

nObs = size(mat, 1);
code = zeros(nObs, 1);

for iObs = 1:nObs
    mat_i = mat(iObs, :);
    code(iObs, 1) = str2double( ...
        strrep(strcat(num2str(mat_i == 1)), ' ', ''));
end

end
